//
// Created by Administrator on 2024/11/19.
//
#include "SearchId.h"


std::string SearchId::convert_string_to_base62_by_sha1(const std::string &input) {
    std::string sha1 = OpenSSLUtils::sha1Hash(input);
    std::stringstream sid_hex;
    sid_hex << std::hex  << sha1;
    mpz_class num;
    num.set_str(sid_hex.str(), 16);
    return base62_encode(num, BASE62);
}

std::string SearchId::crc32_checksum(const std::string &input) {
    uLong crc = crc32(0L, Z_NULL, 0);
    crc = crc32(crc, reinterpret_cast<const Bytef *>(input.c_str()), input.length());

    std::stringstream ss;
    ss << std::setfill('0') << std::setw(8) << std::hex << crc;
    return ss.str();
}


std::string SearchId::base62_encode(const mpz_class &n, const std::string &charset) {
    const int BASE = static_cast<int>(charset.size());
    std::vector<char> chs;

    if (n == 0) {
        return "0";
    }

    mpz_class num = n;
    while (num > 0) {
        mpz_class remainder = num % BASE;
        chs.insert(chs.begin(), charset[remainder.get_ui()]);
        num /= BASE;
    }

    return {chs.begin(), chs.end()};
}

std::string SearchId::base62_decode(const std::string &str, const std::string &charset) {
    const int BASE = static_cast<int>(charset.size());

    mpz_class num = 0;
    for (char c: str) {
        size_t index = charset.find(c);
        if (index == std::string::npos) {
            throw std::invalid_argument("Character not found in charset.");
        }
        num = num * BASE + index;
    }

    return num.get_str(16);
}


std::string SearchId::expired_encode(const std::string &search_id, unsigned int life_span) {

    std::time_t timestamp = std::time(nullptr);
    std::stringstream expired;
    unsigned int expired_time = timestamp + life_span * 60;
    expired << std::hex << std::setw(8) << std::setfill('0') << expired_time;

    auto seed = search_id + expired.str() + SALT;
    auto checksum = SearchId::crc32_checksum(seed);
    std::stringstream checksumCrc32;
    checksumCrc32 << std::hex << std::setw(8) << std::setfill('0') << checksum;

    std::stringstream sid_hex;
    sid_hex << std::hex << std::setw(8) << std::setfill('0') << search_id;
    if (sid_hex.str().size() != 54) {
        sid_hex << std::hex << std::setw((54 - static_cast<int>(54 - sid_hex.str().size()))) << std::setfill('0');
    }
    auto sidHex = SearchId::StringToHex(sid_hex.str());
    auto decoded_hex = checksumCrc32.str() + expired.str() + sidHex;

    mpz_class num;
    num.set_str(decoded_hex.c_str(), 16);

    return SearchId::base62_encode(num, BASE62);
}

std::vector<unsigned char> SearchId::hexToBytes(const std::string &hex) {
    std::vector<unsigned char> bytes;
    for (size_t i = 0; i < hex.length(); i += 2) {
        unsigned char byte = (unsigned char) (std::stoi(hex.substr(i, 2), nullptr, 16));
        bytes.push_back(byte);
    }
    return bytes;
}

std::string SearchId::StringToHex(const std::string &data) {
    const std::string hex = "0123456789abcdef";
    std::stringstream ss;

    for (char i: data) {
        ss << hex[(unsigned char) i >> 4] << hex[(unsigned char) i & 0xf];
    }
    return ss.str();
}

std::string SearchId::HexToStr(const std::string &str) {
    std::string result;
    for (size_t i = 0; i < str.length(); i += 2) {
        std::string byte = str.substr(i, 2);
        char chr = (char) (int) strtol(byte.c_str(), NULL, 16);
        result.push_back(chr);
    }
    return result;
}


std::vector<std::string> SearchId::expired_decode(const std::string &encode_str) {
    auto decoded_hex = base62_decode(encode_str, BASE62);
    auto expiredTimeHex = decoded_hex.substr(8, 8);
    auto sid = SearchId::hex_to_bytes(decoded_hex.substr(16));
    auto seed = sid + expiredTimeHex + SALT;
    return {decoded_hex.substr(0, 8), seed, sid, expiredTimeHex};
}

std::string SearchId::hex_to_bytes(const std::string &hex) {
    std::string result;
    for (size_t i = 0; i < hex.length(); i += 2) {
        unsigned char byte = std::stoi(hex.substr(i, 2), nullptr, 16);
        result.push_back(static_cast<char>(byte));
    }
    return result;
}

